﻿using System;
using System.Configuration;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Query;

namespace PluginRegistrationTool
{
    /// <summary>
    /// The Dynamics CRM Service Class.
    /// 
    /// This class provides constructors, properties and methods for establishing a Dynamics CRM service and using its methods.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Xrm")]
    public class XrmService : IDisposable
    {
        #region Constructors

        /// <summary>
        /// XrmService Class Constructor.
        /// </summary>
        public XrmService()
        {
            if (ConfigurationManager.AppSettings.Count != 0)
            {
                this.ServerAddress = ConfigurationManager.AppSettings["ServerUrl"];
                this.OrganizationName = ConfigurationManager.AppSettings["Organization"];
                this.isSecure = false;
            }
        }

        /// <summary>
        /// XrmService Class Constructor.
        /// </summary>
        /// <param name="serverUrl">Dynamics CRM Server URL.</param>
        /// <param name="organization">Organisation Name.</param>
        public XrmService(Uri serverUrl, string organization)
        {
            if (serverUrl != null)
            {
                this.ServerAddress = serverUrl.AbsoluteUri;
                this.OrganizationName = organization;
                this.isSecure = false;
            }
        }

        /// <summary>
        /// XrmService Class Constructor overloaded.
        /// </summary>
        /// <param name="serverUrl">Dynamics CRM Server URL.</param>
        /// <param name="organization">Organisation Name.</param>
        /// <param name="domain">AD Domain.</param>
        /// <param name="userName">User name.</param>
        /// <param name="password">Password.</param>
        public XrmService(Uri serverUrl, string organization, string domain, string userName, string password)
        {
            if (serverUrl != null)
            {
                this.ServerAddress = serverUrl.AbsoluteUri;
                this.OrganizationName = organization;
                this.Domain = domain;
                this.UserName = userName;
                this.Password = password;
                this.isSecure = false;
            }
        }

        /// <summary>
        /// XrmService Class Constructor overloaded.
        /// </summary>
        /// <param name="serverUrl">Dynamics CRM Server URL.</param>
        /// <param name="organization">Organisation Name.</param>
        /// <param name="deviceDomain">Device AD Domain.</param>
        /// <param name="deviceUserName">Device user name.</param>
        /// <param name="devicePassword">Device password.</param>
        /// <param name="useDeviceCredentials">Boolean - use device credentials?</param>
        public XrmService(Uri serverUrl, string organization, string deviceDomain, string deviceUserName, string devicePassword, bool useDeviceCredentials)
        {
            if (serverUrl != null)
            {
                this.ServerAddress = serverUrl.AbsoluteUri;
                this.OrganizationName = organization;
                this.DeviceDomain = deviceDomain;
                this.DeviceUserName = deviceUserName;
                this.DevicePassword = devicePassword;
                this.UseDeviceCredentials = useDeviceCredentials;
                this.isSecure = false;
            }
        }

        /// <summary>
        /// XrmService Class Constructor overloaded.
        /// </summary>
        /// <param name="serverUrl">Dynamics CRM Server URL.</param>
        /// <param name="organization">Organisation Name.</param>
        /// <param name="domain">AD Domain.</param>
        /// <param name="userName">User name.</param>
        /// <param name="password">Password.</param>
        /// <param name="deviceDomain">Device AD Domain.</param>
        /// <param name="deviceUserName">Device user name.</param>
        /// <param name="devicePassword">Device password.</param>
        public XrmService(Uri serverUrl, string organization, string domain, string userName, string password, string deviceDomain, string deviceUserName, string devicePassword)
        {
            if (serverUrl != null)
            {
                this.ServerAddress = serverUrl.AbsoluteUri;
                this.OrganizationName = organization;
                this.Domain = domain;
                this.UserName = userName;
                this.Password = password;
                this.DeviceDomain = deviceDomain;
                this.DeviceUserName = deviceUserName;
                this.DevicePassword = devicePassword;
                this.isSecure = false;
            }
        }

        /// <summary>
        /// XrmService Class Constructor.
        /// </summary>
        public XrmService(bool isSecure)
        {
            if (ConfigurationManager.AppSettings.Count != 0)
            {
                this.ServerAddress = ConfigurationManager.AppSettings["ServerUrl"];
                this.OrganizationName = ConfigurationManager.AppSettings["Organization"];
                this.isSecure = isSecure;
            }
        }

        /// <summary>
        /// XrmService Class Constructor.
        /// </summary>
        /// <param name="serverUrl">Dynamics CRM Server URL.</param>
        /// <param name="organization">Organisation Name.</param>
        /// <param name="isSecure">if set to <c>true</c> [is secure].</param>
        public XrmService(Uri serverUrl, string organization, bool isSecure)
        {
            if (serverUrl != null)
            {
                this.ServerAddress = serverUrl.AbsoluteUri;
                this.OrganizationName = organization;
                this.isSecure = isSecure;
            }
        }

        /// <summary>
        /// XrmService Class Constructor overloaded.
        /// </summary>
        /// <param name="serverUrl">Dynamics CRM Server URL.</param>
        /// <param name="organization">Organisation Name.</param>
        /// <param name="domain">AD Domain.</param>
        /// <param name="userName">User name.</param>
        /// <param name="password">Password.</param>
        /// <param name="isSecure">if set to <c>true</c> [is secure].</param>
        public XrmService(Uri serverUrl, string organization, bool isSecure, string domain, string userName, string password)
        {
            if (serverUrl != null)
            {
                this.ServerAddress = serverUrl.AbsoluteUri;
                this.OrganizationName = organization;
                this.Domain = domain;
                this.UserName = userName;
                this.Password = password;
                this.isSecure = isSecure;
            }
        }

        /// <summary>
        /// XrmService Class Constructor overloaded.
        /// </summary>
        /// <param name="serverUrl">Dynamics CRM Server URL.</param>
        /// <param name="organization">Organisation Name.</param>
        /// <param name="deviceDomain">Device AD Domain.</param>
        /// <param name="deviceUserName">Device user name.</param>
        /// <param name="devicePassword">Device password.</param>
        /// <param name="useDeviceCredentials">Boolean - use device credentials?</param>
        /// <param name="isSecure">if set to <c>true</c> [is secure].</param>
        public XrmService(Uri serverUrl, string organization, string deviceDomain, string deviceUserName, string devicePassword, bool useDeviceCredentials, bool isSecure)
        {
            if (serverUrl != null)
            {
                this.ServerAddress = serverUrl.AbsoluteUri;
                this.OrganizationName = organization;
                this.DeviceDomain = deviceDomain;
                this.DeviceUserName = deviceUserName;
                this.DevicePassword = devicePassword;
                this.UseDeviceCredentials = useDeviceCredentials;
                this.isSecure = isSecure;
            }
        }

        /// <summary>
        /// XrmService Class Constructor overloaded.
        /// </summary>
        /// <param name="serverUrl">Dynamics CRM Server URL.</param>
        /// <param name="organization">Organisation Name.</param>
        /// <param name="domain">AD Domain.</param>
        /// <param name="userName">User name.</param>
        /// <param name="password">Password.</param>
        /// <param name="deviceDomain">Device AD Domain.</param>
        /// <param name="deviceUserName">Device user name.</param>
        /// <param name="devicePassword">Device password.</param>
        /// <param name="isSecure">if set to <c>true</c> [is secure].</param>
        public XrmService(Uri serverUrl, string organization, string domain, string userName, string password, string deviceDomain, string deviceUserName, string devicePassword, bool isSecure)
        {
            if (serverUrl != null)
            {
                this.ServerAddress = serverUrl.AbsoluteUri;
                this.OrganizationName = organization;
                this.Domain = domain;
                this.UserName = userName;
                this.Password = password;
                this.DeviceDomain = deviceDomain;
                this.DeviceUserName = deviceUserName;
                this.DevicePassword = devicePassword;
                this.isSecure = isSecure;
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Domain string property.
        /// </summary>
        private string domain;

        /// <summary>
        /// Domain string property.
        /// </summary>
        private string Domain
        {
            get
            {
                return domain;
            }
            set
            {
                domain = value;
            }
        }

        /// <summary>
        /// User name string property.
        /// </summary>
        private string userName;

        /// <summary>
        /// User name string property.
        /// </summary>
        private string UserName
        {
            get
            {
                return userName;
            }
            set
            {
                userName = value;
            }
        }

        /// <summary>
        /// Password string property.
        /// </summary>
        private string password;

        /// <summary>
        /// Password string property.
        /// </summary>
        private string Password
        {
            get
            {
                return password;
            }
            set
            {
                password = value;
            }
        }

        /// <summary>
        /// Device domain string property.
        /// </summary>
        private string deviceDomain;

        /// <summary>
        /// Device domain string property.
        /// </summary>
        private string DeviceDomain
        {
            get
            {
                return deviceDomain;
            }
            set
            {
                deviceDomain = value;
            }
        }

        /// <summary>
        /// Device user name string property.
        /// </summary>
        private string deviceUserName;

        /// <summary>
        /// Device user name string property.
        /// </summary>
        private string DeviceUserName
        {
            get
            {
                return deviceUserName;
            }
            set
            {
                deviceUserName = value;
            }
        }

        /// <summary>
        /// Device password string property.
        /// </summary>
        private string devicePassword;

        /// <summary>
        /// Device password string property.
        /// </summary>
        private string DevicePassword
        {
            get
            {
                return devicePassword;
            }
            set
            {
                devicePassword = value;
            }
        }

        /// <summary>
        /// Server address string property.
        /// </summary>
        private string serverAddress;

        /// <summary>
        /// Server address string property.
        /// </summary>
        private string ServerAddress
        {
            set
            {
                serverAddress = value;
            }
        }

        /// <summary>
        /// Organisation name string property.
        /// </summary>
        private string organizationName;

        /// <summary>
        /// Organisation name string property.
        /// </summary>
        private string OrganizationName
        {
            set
            {
                organizationName = value;
            }
        }

        private bool isSecure;

        public bool IsSecure
        {
            get
            {
                return isSecure;
            }
            set
            {
                isSecure = value;
            }
        }

        /// <summary>
        /// Server connection string property.
        /// </summary>
        private ServerConnection serverConnection;

        /// <summary>
        /// Server connection string property.
        /// </summary>
        private ServerConnection ServerConnection
        {
            get
            {
                if (string.IsNullOrEmpty(Domain) && string.IsNullOrEmpty(UserName) && string.IsNullOrEmpty(Password))
                {
                    if (string.IsNullOrEmpty(DeviceDomain) && string.IsNullOrEmpty(DeviceUserName) && string.IsNullOrEmpty(DevicePassword))
                    {
                        serverConnection = new ServerConnection(serverAddress, organizationName, isSecure);
                    }
                    else
                    {
                        serverConnection = new ServerConnection(serverAddress, organizationName, DeviceDomain, DeviceUserName, DevicePassword, true, isSecure);
                    }
                }
                else
                {
                    if (string.IsNullOrEmpty(DeviceDomain) && string.IsNullOrEmpty(DeviceUserName) && string.IsNullOrEmpty(DevicePassword))
                    {
                        serverConnection = new ServerConnection(serverAddress, organizationName, Domain, UserName, Password, isSecure);
                    }
                    else
                    {
                        serverConnection = new ServerConnection(serverAddress, organizationName, Domain, UserName, Password, DeviceDomain, DeviceUserName, DevicePassword, isSecure);
                    }
                }
                return ServerConnection;
            }
        }

        /// <summary>
        /// Organisation service proxy.
        /// </summary>
        private OrganizationServiceProxy organizationServiceProxy;

        /// <summary>
        /// Organisation service proxy.
        /// </summary>
        public OrganizationServiceProxy OrganizationServiceProxy
        {
            get
            {
                organizationServiceProxy = new OrganizationServiceProxy(ServerConnection.Configuration.OrganizationUri, ServerConnection.Configuration.HomeRealmUri, ServerConnection.Configuration.Credentials, ServerConnection.Configuration.DeviceCredentials);
                organizationServiceProxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(new ProxyTypesBehavior());
                organizationServiceProxy.Timeout = new TimeSpan(0, 10, 0);
                return organizationServiceProxy;
            }
        }

        /// <summary>
        /// Organisation service context.
        /// </summary>
        private OrganizationServiceContext organizationServiceContext;

        /// <summary>
        /// Organisation service context.
        /// </summary>
        public OrganizationServiceContext OrganizationServiceContext
        {
            get
            {
                organizationServiceContext = new OrganizationServiceContext(OrganizationServiceProxy);
                return organizationServiceContext;
            }
        }

        /// <summary>
        /// Use Device Credentials.
        /// </summary>
        private bool useDeviceCredentials;

        /// <summary>
        /// Use Device Credentials.
        /// </summary>
        public bool UseDeviceCredentials
        {
            get
            {
                return useDeviceCredentials;
            }
            set
            {
                useDeviceCredentials = value;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Associate two or more entity records.
        /// </summary>
        /// <param name="entityName">The name of the entity that contains the record.</param>
        /// <param name="entityId">The id of the record being associated.</param>
        /// <param name="relationship">The relatiomship to use for association.</param>
        /// <param name="relatedEntities">The collection of entities to associate the record with.</param>
        public void Associate(string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities)
        {
            try
            {
                using (OrganizationServiceProxy organizationServiceProxy = this.OrganizationServiceProxy)
                {
                    organizationServiceProxy.Associate(entityName, entityId, relationship, relatedEntities);
                }
            }
            catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)
            {
                throw;
            }
        }

        /// <summary>
        /// Create an entity record.
        /// </summary>
        /// <param name="entity">The entity object containing the details of the record to be created.</param>
        /// <returns>The record id.</returns>
        public Guid Create(Entity entity)
        {
            try
            {
                Guid entityId;
                using (OrganizationServiceProxy organizationServiceProxy = this.OrganizationServiceProxy)
                {
                    entityId = organizationServiceProxy.Create(entity);
                }
                return entityId;
            }
            catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)
            {
                throw;
            }
        }

        /// <summary>
        /// Delete an entity record.
        /// </summary>
        /// <param name="entityName">The name of the entity the record exists in.</param>
        /// <param name="entityRecordId">The record id.</param>
        public void Delete(string entityName, Guid entityRecordId)
        {
            try
            {
                using (OrganizationServiceProxy organizationServiceProxy = this.OrganizationServiceProxy)
                {
                    organizationServiceProxy.Delete(entityName, entityRecordId);
                }
            }
            catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)
            {
                throw;
            }
        }

        /// <summary>
        /// Disassociate two or more entitity records.
        /// </summary>
        /// <param name="entityName">The name of the entity that contains the record.</param>
        /// <param name="entityId">The id of the record being disassociated.</param>
        /// <param name="relationship">The relatiomship being use for the current association.</param>
        /// <param name="relatedEntities">The collection of entities to disassociate the record from.</param>
        public void Disassociate(string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities)
        {
            try
            {
                using (OrganizationServiceProxy organizationServiceProxy = this.OrganizationServiceProxy)
                {
                    organizationServiceProxy.Disassociate(entityName, entityId, relationship, relatedEntities);
                }
            }
            catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)
            {
                throw;
            }
        }

        /// <summary>
        /// Execute an organisation request.
        /// </summary>
        /// <param name="organizationRequest">The organisation request to be executed.</param>
        /// <returns>The organisation response.</returns>
        public OrganizationResponse Execute(OrganizationRequest organizationRequest)
        {
            try
            {
                OrganizationResponse response;
                using (OrganizationServiceProxy organizationServiceProxy = this.OrganizationServiceProxy)
                {
                    response = organizationServiceProxy.Execute(organizationRequest);
                }
                return response;
            }
            catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)
            {
                throw;
            }
        }

        /// <summary>
        /// Retrieves an entity object containing the details of a specific record.
        /// </summary>
        /// <param name="entityName">The name of the entity to which the record belongs.</param>
        /// <param name="entityId">The record id.</param>
        /// <param name="columns">The fields to be included in the returned entity object.</param>
        /// <returns>An entity object containing the details of a specific record.</returns>
        public Entity Retrieve(string entityName, Guid entityId, ColumnSet columns)
        {
            try
            {
                Entity entity;
                using (OrganizationServiceProxy organizationServiceProxy = this.OrganizationServiceProxy)
                {
                    entity = organizationServiceProxy.Retrieve(entityName, entityId, columns);
                }
                return entity;
            }
            catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)
            {
                throw;
            }
        }

        /// <summary>
        /// Retrieves multiple records based on a query.
        /// </summary>
        /// <param name="query">The query.</param>
        /// <returns>An entity collection object containing multiple records.</returns>
        public EntityCollection RetrieveMultiple(QueryBase query)
        {
            try
            {
                EntityCollection entityCollection;
                using (OrganizationServiceProxy organizationServiceProxy = this.OrganizationServiceProxy)
                {
                    entityCollection = organizationServiceProxy.RetrieveMultiple(query);
                }
                return entityCollection;
            }
            catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)
            {
                throw;
            }
        }

        /// <summary>
        /// Update an entity record.
        /// </summary>
        /// <param name="entity">The entity object containing the details of the record to be updated.</param>
        public void Update(Entity entity)
        {
            try
            {
                using (OrganizationServiceProxy organizationServiceProxy = this.OrganizationServiceProxy)
                {
                    organizationServiceProxy.Update(entity);
                }
            }
            catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)
            {
                throw;
            }
        }

        #endregion

        #region Disposal

        /// <summary>
        /// Dispose.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Finalize.
        /// </summary>
        ~XrmService()
        {
            Dispose(false);
        }


        /// <summary>
        /// Dispose.
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (OrganizationServiceProxy != null)
                {
                    OrganizationServiceProxy.Dispose();
                }
                if (OrganizationServiceContext != null)
                {
                    OrganizationServiceContext.Dispose();
                }
                if (organizationServiceProxy != null)
                {
                    organizationServiceProxy.Dispose();
                    organizationServiceProxy = null;
                }
                if (organizationServiceContext != null)
                {
                    organizationServiceContext.Dispose();
                    organizationServiceContext = null;
                }
            }
        }

        #endregion
    }
}
